package shaw.wifi.network;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;

import shaw.wifi.Constants;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiManager;
import android.util.Log;

/* This class will hold the new network settings and will create
 * a new remembered network (or update an old network) with these
 * settings on the phone.
 */

public class NetworkSettings {	
	//possible authentication types
	public static final int WIFI_WPA 	= 0;	//wpa (uses preshared key)
	public static final int WIFI_PEAP	= 1;	//peap (uses username/password setup)
	
	//the authentication type being applied
	protected int wifiType = WIFI_WPA;
	
	//network information
	protected String ssid;
	protected String sharedKey;
	
	protected String peapUsername;
	protected String peapPassword;
	protected boolean hidden;
	
	//this function will remove the quotes from a string which is encased
	//by quotes: "ssid" will become ssid
	public static String stripQuotes(String s) {
		s = s.substring(1, s.length()-1);
		return s;
	}
	
	public NetworkSettings() {}
	public NetworkSettings(String ssid, String sharedKey) {
		this.ssid = ssid;
		this.sharedKey = sharedKey;
	}
	
	public void setType(int type) {
		wifiType = type;
	}
	
	public void setSSID(String ssid) {
		this.ssid = ssid;
	}
	public void setPSK(String key) {
		this.sharedKey = key;
	}
	public void setPeapUser(String u) {
		peapUsername = u;
	}
	public void setPeapPass(String p) {
		peapPassword = p;
	}
	
	public boolean apply(WifiManager wifi, boolean disableOpen) {	
		//get current networks remembered by the phone
		List<WifiConfiguration> networkList = wifi.getConfiguredNetworks();
		
		//search for a network with the same ssid as the new network (so that
		//the phone can remove that entry before creating the new entry)
		for(WifiConfiguration wc : networkList) {
			Log.d("apply", "comparing " + stripQuotes(wc.SSID) + " to " + ssid);
			//the network ssids from the wifimanager come in the form of "ssid", so
			//we strip the quotes. I was running into difficulties doing a comparison
			//by adding quotes to the new ssid, so we strip the quotes from the remembered
			//ssid and then compare.
			if(stripQuotes(wc.SSID).equals(ssid)) {
				//remove existing network with same ssid.
				Log.d("apply", "forgetting existing network...");
				wifi.removeNetwork(wc.networkId);
			}
			if(stripQuotes(wc.SSID).equals(Constants._OPENSSID) && disableOpen == true) {
				Log.e("APPLY", "FORGETTING OPEN");
				wifi.removeNetwork(wc.networkId);
			}
		}
		
		//apply a given set of network settings
		switch (wifiType) {
		case WIFI_WPA:
			return applyWPA(wifi, disableOpen);
		case WIFI_PEAP:
			return applyPeap(wifi, disableOpen);
		default:
			return false;
		}
	}
	
	private boolean applyWPA(WifiManager wifi, boolean disableOpen) {
		//applies wpa-psk settings
		String setSSID = "\"" + ssid + "\"";
    	String setKey = "\"" + sharedKey + "\"";
    	//for now we assume this is not a hidden network
    	//(the ssid is broadcasted)
    	boolean isHidden = false;
    	
    	Log.d("set nw", "adding " + setSSID + " (key) " + setKey);
    	
    	//create a new wifi configuration
    	WifiConfiguration wc = new WifiConfiguration();
    	
    	//set ssid, key and hidden status
    	wc.SSID = setSSID;
    	wc.preSharedKey = setKey; 
    	wc.hiddenSSID = isHidden;
    	
    	//set the status to enabled
    	wc.status = WifiConfiguration.Status.ENABLED;    
    	
    	//Additional wifi configuration data could be set based on the
    	//settings of the actual network. For now the defaults work ok because they
    	//provide support for almost everything.
    	
    	//ie: 
    	//	wc.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP);
    	//	wc.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
    	
    	//add the network, res should be the networkId at this point, or
    	// -1 if the add failed.
    	int res = wifi.addNetwork(wc);
    	Log.d("WifiPreference", "add Network returned " + res );
    	
    	//enable the network res, false means don't disable other networks
    	boolean b = wifi.enableNetwork(res, disableOpen);        
    	Log.d("WifiPreference", "enableNetwork returned " + b );
    	
    	//return whether the network has been successfully enabled
    	return res != -1;
	}
	
	@SuppressWarnings("rawtypes")
	private boolean applyPeap(WifiManager wifi, boolean disableOpen) {
		Log.e("PEAP", "APPLYING PEAP!");
		
		//we use these fields to identify the private fields in the wificonfiguration below via reflection
		final String INT_PRIVATE_KEY = "private_key";
	    final String INT_PHASE2 = "phase2";
	    final String INT_PASSWORD = "password";
	    final String INT_IDENTITY = "identity";
	    final String INT_EAP = "eap";
	    final String INT_CLIENT_CERT = "client_cert";
	    final String INT_CA_CERT = "ca_cert";
	    final String INT_ANONYMOUS_IDENTITY = "anonymous_identity";
	    final String INT_ENTERPRISEFIELD_NAME = "android.net.wifi.WifiConfiguration$EnterpriseField";
		    
	    /********************************Configuration Strings****************************************************/
	    //out values. We know we are using PEAP, the server handles the inner auth method and just asks for a password
        final String ENTERPRISE_EAP = "PEAP";
        final String ENTERPRISE_CLIENT_CERT = null;
        final String ENTERPRISE_PRIV_KEY = null;
        final String ENTERPRISE_PHASE2 = null;
        final String ENTERPRISE_ANON_IDENT = null;
        final String ENTERPRISE_CA_CERT = null;

        //set the ssid and hidden values
        String setSSID = "\"" + ssid + "\"";

        WifiConfiguration wc = new WifiConfiguration();
        wc.SSID = setSSID;
        wc.hiddenSSID = hidden;

        Log.e("NS", peapUsername + " : " + peapPassword);
        
        /*Key Mgmnt*/
        wc.allowedKeyManagement.clear();
        wc.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.IEEE8021X);
        wc.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);

        // Enterprise Settings
        // Reflection magic here too, need access to non-public APIs
        try {
            // Let the magic start
            Class[] wcClasses = WifiConfiguration.class.getClasses();
            // null for overzealous java compiler
            Class wcEnterpriseField = null;

            for (Class wcClass : wcClasses)
                if (wcClass.getName().equals(INT_ENTERPRISEFIELD_NAME)) 
                {
                    wcEnterpriseField = wcClass;
                    break;
                }
            boolean noEnterpriseFieldType = false; 
            if(wcEnterpriseField == null)
                noEnterpriseFieldType = true; // Cupcake/Donut access enterprise settings directly

            Field wcefAnonymousId = null, wcefCaCert = null, wcefClientCert = null, wcefEap = null, wcefIdentity = null, wcefPassword = null, wcefPhase2 = null, wcefPrivateKey = null;
            Field[] wcefFields = WifiConfiguration.class.getFields();
            // Dispatching Field vars
            for (Field wcefField : wcefFields) 
            {
                if (wcefField.getName().equals(INT_ANONYMOUS_IDENTITY))
                    wcefAnonymousId = wcefField;
                else if (wcefField.getName().equals(INT_CA_CERT))
                    wcefCaCert = wcefField;
                else if (wcefField.getName().equals(INT_CLIENT_CERT))
                    wcefClientCert = wcefField;
                else if (wcefField.getName().equals(INT_EAP))
                    wcefEap = wcefField;
                else if (wcefField.getName().equals(INT_IDENTITY))
                    wcefIdentity = wcefField;
                else if (wcefField.getName().equals(INT_PASSWORD))
                    wcefPassword = wcefField;
                else if (wcefField.getName().equals(INT_PHASE2))
                    wcefPhase2 = wcefField;
                else if (wcefField.getName().equals(INT_PRIVATE_KEY))
                    wcefPrivateKey = wcefField;
            }


            Method wcefSetValue = null;
            if(!noEnterpriseFieldType){
            for(Method m: wcEnterpriseField.getMethods())
                //System.out.println(m.getName());
                if(m.getName().trim().equals("setValue"))
                    wcefSetValue = m;
            }

            //could use these values for other authentication methods (using cerficiates)
            
            /*EAP Method*/
            if(!noEnterpriseFieldType)
                wcefSetValue.invoke(wcefEap.get(wc), ENTERPRISE_EAP);

            /*EAP Phase 2 Authentication*/
            if(!noEnterpriseFieldType)
                wcefSetValue.invoke(wcefPhase2.get(wc), ENTERPRISE_PHASE2);

            /*EAP Anonymous Identity*/
            if(!noEnterpriseFieldType)
                wcefSetValue.invoke(wcefAnonymousId.get(wc), ENTERPRISE_ANON_IDENT);

            /*EAP CA Certificate*/
            if(!noEnterpriseFieldType)
                wcefSetValue.invoke(wcefCaCert.get(wc), ENTERPRISE_CA_CERT);

            /*EAP Private key*/
            if(!noEnterpriseFieldType)
                wcefSetValue.invoke(wcefPrivateKey.get(wc), ENTERPRISE_PRIV_KEY);

            /*EAP Identity*/
            if(!noEnterpriseFieldType)
                wcefSetValue.invoke(wcefIdentity.get(wc), peapUsername);

            /*EAP Password*/
            if(!noEnterpriseFieldType)
                wcefSetValue.invoke(wcefPassword.get(wc), peapPassword);

            /*EAp Client certificate*/
            if(!noEnterpriseFieldType)
                wcefSetValue.invoke(wcefClientCert.get(wc), ENTERPRISE_CLIENT_CERT);

            // Adhoc for CM6
            // if non-CM6 fails gracefully thanks to nested try-catch

            //this is added for cyanogen mod, saying that the network is not an adhoc network
            
            try{
            Field wcAdhoc = WifiConfiguration.class.getField("adhocSSID");
            Field wcAdhocFreq = WifiConfiguration.class.getField("frequency");
            wcAdhoc.setBoolean(wc, false);
            int freq = 2462;    // default to channel 11
            wcAdhocFreq.setInt(wc, freq); 
            } catch (Exception e)
            {
                //no cm6...ignoring
            }

        } catch (Exception e)
        {
            e.printStackTrace();
        }
        
        int res = wifi.addNetwork(wc);
        Log.d("WifiPreference", "add Network returned " + res );
        
        //we save the configuration and disable the previous network, enabling shaw secure
        //this means that we disconnect from shaw_open and connect automatically and seamlessly
        //to shaw secure.
        
        if(res != -1) {
	        boolean b = wifi.enableNetwork(wc.networkId, false);
	        Log.d("WifiPreference", "enableNetwork returned " + b );
	        boolean c = wifi.saveConfiguration();
	        Log.d("WifiPreference", "Save configuration returned " + c );
	        boolean d = wifi.enableNetwork(res, true);   
	        Log.d("WifiPreference", "enableNetwork returned " + d );  
        }
		
		return res != -1;
	}
}
